package com.codbking.calendar.exaple.calendar;

import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ListView;

import static com.codbking.calendar.exaple.calendar.CalendarLayout.OpenType.FOLD;
import static com.codbking.calendar.exaple.calendar.CalendarLayout.OpenType.FULL_SCREEN;

public class CalendarLayout extends FrameLayout {

    private static final String TAG = "CalendarLayout";

    private CalendarDateView calendarDateView;
    private ViewGroup view2;
    private OpenType openType = FOLD;

    private boolean isSliding = false;

    private int openHeight;
    private int foldHeight;
    private int bottomViewDistanceFromTopViewHeight;
    private int maxOpenDistance;
    private int maxFullScreenDistance;
    private int fullScreenHeight;

    private ScrollerCompat mScroller;

    private static final Interpolator sInterpolator = new Interpolator() {
        @Override
        public float getInterpolation(float t) {
            t -= 1.0f;
            return t * t * t * t * t + 1.0f;
        }
    };

    public CalendarLayout(Context context) {
        super(context);
        init();
    }

    public CalendarLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mScroller = ScrollerCompat.create(getContext(), sInterpolator);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        calendarDateView = (CalendarDateView) getChildAt(0);
        view2 = (ViewGroup) getChildAt(1);

        this.calendarDateView.setCalendarTopViewChangeListener(new CalendarTopViewChangeListener() {
            @Override
            public void onLayoutChange(CalendarDateView topView) {
            }

            @Override
            public boolean isFullScreen() {
                return openType == FULL_SCREEN;
            }

            @Override
            public boolean isFold() {
                return openType == FOLD;
            }

        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        fullScreenHeight = MeasureSpec.getSize(heightMeasureSpec);
        Log.d(TAG, "onMeasure" + fullScreenHeight);
        foldHeight = calendarDateView.getOpenItemHeight();
        openHeight = calendarDateView.getOpenItemHeight() * 6;
        maxOpenDistance = openHeight - foldHeight;
        maxFullScreenDistance = fullScreenHeight - openHeight;
        view2.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec)
                - calendarDateView.getOpenItemHeight(), MeasureSpec.EXACTLY));

        switch (openType) {
            case FOLD:
                bottomViewDistanceFromTopViewHeight = foldHeight;
                break;
            case OPEN:
                bottomViewDistanceFromTopViewHeight = openHeight;
                break;
            case FULL_SCREEN:
                bottomViewDistanceFromTopViewHeight = fullScreenHeight;
            default:
                break;
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.d(TAG, "onLayout");
        super.onLayout(changed, left, top, right, bottom);
        view2.offsetTopAndBottom(bottomViewDistanceFromTopViewHeight);
        int[] selectRct = getSelectItemRect();
        if (openType == FOLD) {
            calendarDateView.offsetTopAndBottom(-selectRct[1]);
        } else if (openType == OpenType.OPEN) {
            calendarDateView.offsetTopAndBottom(0);
        } else if (openType == OpenType.FULL_SCREEN) {
            calendarDateView.offsetTopAndBottom(0);
        }
    }

    private float oY, oX;
    private boolean isClickBottomViewArea = false;
    private ScrollDirection scrollDirection = ScrollDirection.NO;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isFlag = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                oY = ev.getY();
                oX = ev.getX();
                isClickBottomViewArea = isClickViewArea(view2, ev);

                freshOpenType();
                Log.d(TAG, "onInterceptTouchEvent ACTION_DOWN " + " open type : " + openType.getName()
                        + ", top " + view2.getTop() + ", open height " + openHeight + ", full screen height " + fullScreenHeight);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onInterceptTouchEvent ACTION_MOVE ");
                freshOpenType();

                float y = ev.getY();
                float x = ev.getX();

                float xOffset = x - oX;
                float yOffset = y - oY;

                if (Math.abs(yOffset) > 5 && Math.abs(yOffset) > Math.abs(xOffset)) {
                    isFlag = true;
                    if (yOffset > 0) {
                        scrollDirection = ScrollDirection.DOWN;
                    } else if (yOffset < 0) {
                        scrollDirection = ScrollDirection.UP;
                    } else {
                        scrollDirection = ScrollDirection.NO;
                    }
                    Log.d(TAG, "onInterceptTouchEvent ACTION_MOVE " + scrollDirection.getName());
                    if (isClickBottomViewArea) {
                        boolean isScroll = isBottomViewScroll(view2);
                        oX = x;
                        oY = y;

                        if (yOffset > 0) {
                            if (openType == OpenType.FULL_SCREEN) {
                                return false;
                            } else if (openType == OpenType.OPEN) {
                                return true;
                            } else if (openType == FOLD) {
                                return !isScroll;
                            }
                        } else {
                            if (openType == OpenType.FULL_SCREEN) {
                                return true;
                            } else if (openType == OpenType.OPEN) {
                                return true;
                            } else if (openType == FOLD) {
                                return false;
                            }
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onInterceptTouchEvent ACTION_UP ");
                scrollDirection = ScrollDirection.NO;
                break;
        }
        return isSliding || isFlag;
    }

    private boolean isBottomViewScroll(ViewGroup view2) {
        View firstChildView = view2.getChildAt(0);
        if (firstChildView == null) {
            return false;
        }

        if (view2 instanceof ListView) {
            AbsListView list = (AbsListView) view2;
            if (firstChildView.getTop() != 0) {
                return true;
            } else {
                if (list.getPositionForView(firstChildView) != 0) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isClickViewArea(View view, MotionEvent ev) {
        Rect rect = new Rect();
        view.getHitRect(rect);
        boolean isClick = rect.contains((int) ev.getX(), (int) ev.getY());
        Log.d(TAG, "isClickViewArea() called with: isClick = [" + isClick + "]");
        return isClick;
    }

    private void freshOpenType() {
        if (view2.getTop() < openHeight) {
            openType = FOLD;
        } else if (view2.getTop() >= openHeight && view2.getTop() < fullScreenHeight) {
            openType = OpenType.OPEN;
        } else {
            openType = OpenType.FULL_SCREEN;
        }

        Log.d(TAG, "freshOpenType view2 top : " + view2.getTop() + " " + fullScreenHeight + " " + openType.getName());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        processTouchEvent(event);
        return true;
    }

    public void processTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                if (isSliding) {
                    return;
                }
                float cY = event.getY();
                int dY = (int) (cY - oY);
                if (dY > 0) {
                    scrollDirection = ScrollDirection.DOWN;
                } else if (dY < 0) {
                    scrollDirection = ScrollDirection.UP;
                } else {
                    scrollDirection = ScrollDirection.NO;
                }
                Log.d(TAG, "processTouchEvent ACTION_MOVE " + scrollDirection.getName());
                if (dY == 0) {
                    return;
                }
                oY = cY;
                move(dY);
                break;
            case MotionEvent.ACTION_UP:
                scrollDirection = ScrollDirection.NO;
                Log.d(TAG, "processTouchEvent ACTION_UP " + " open type : " + openType.getName()
                        + " , scroll direction : " + scrollDirection.getName());

                int line1 = (openHeight - foldHeight) / 2;
                int line2 = (fullScreenHeight + openHeight) / 2;

                if (view2.getTop() >= foldHeight && view2.getTop() <= line1) {
                    fold();
                } else if (view2.getTop() > line1 && view2.getTop() <= openHeight) {
                    open();
                } else if (view2.getTop() > openHeight && view2.getTop() < line2) {
                    foldScreenToOpen();
                } else if (view2.getTop() >= line2 && view2.getTop() <= fullScreenHeight) {
                    fullScreen();
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }
    }

    private void open() {
        Log.d(TAG, "scroll open() ");
        startScroll(view2.getTop(), openHeight);
    }

    private void fold() {
        Log.d(TAG, "scroll fold() ");
        startScroll(view2.getTop(), openHeight - maxOpenDistance);
    }

    private void foldScreenToOpen() {
        Log.d(TAG, "scroll foldScreenToOpen() ");
        startScroll(view2.getTop(), fullScreenHeight - maxFullScreenDistance);
        calendarDateView.setLeftAndRightPageScrollOpenType();
    }

    private void fullScreen() {
        Log.d(TAG, "scroll fullScreen() ");
        startScroll(view2.getTop(), fullScreenHeight);
        calendarDateView.setLeftAndRightPageScrollFullScreenType();
    }

    private void startScroll(int startY, int endY) {
        Log.d(TAG, "startScroll() " + (endY - startY));

        float distance = endY - startY;
        float t = distance / maxOpenDistance * 600;

        mScroller.startScroll(0, 0, 0, endY - startY, (int) Math.abs(t));
        postInvalidate();
    }

    private int[] getSelectItemRect() {
        return calendarDateView.getCurrentSelectPosition();
    }

    private void move(int dY) {
        Log.d(TAG, "move() openType.getName :" + openType.getName());
        Log.d(TAG, "move() dY " + dY);
        int[] selectRect = getSelectItemRect();
        int itemHeight = calendarDateView.getOpenItemHeight();
        int dY1 = calculateScrollDistance(calendarDateView.getTop(), dY, -selectRect[1], 0);
        if (calendarDateView.getTop() >= 0 && openType == OpenType.OPEN && scrollDirection == ScrollDirection.DOWN) {
            dY1 = 0;
        } else if (calendarDateView.getTop() >= 0 && view2.getTop() >= openHeight && scrollDirection == ScrollDirection.UP) {
            dY1 = 0;
        } else if (calendarDateView.getTop() >= 0 && view2.getTop() > openHeight) {
            dY1 = 0;
        }
        //TODO
        int row = 5;
        int dY2 = 0;
        if (openType == FOLD) {
            dY2 = calculateScrollDistance(view2.getTop() - openHeight, dY, -(openHeight - itemHeight), 0);
        } else if (openType == OpenType.OPEN) {
            if (view2.getTop() >= openHeight && dY > 0) {
                int itemScrollY = (dY + row) / row;
                calendarDateView.viewChangeWithScrollDistance(itemScrollY);
                dY2 = calculateScrollDistance(view2.getTop() - fullScreenHeight, itemScrollY * 6, -(fullScreenHeight - openHeight), 0);
            } else if (view2.getTop() > openHeight && dY < 0) {
                dY2 = calculateScrollDistance(view2.getTop() - fullScreenHeight, dY, -(fullScreenHeight - openHeight), 0);
                int itemScrollY = ((dY2) / row) == 0 ? -1 : ((dY2) / row);
                calendarDateView.viewChangeWithScrollDistance(itemScrollY);
            } else if (view2.getTop() <= openHeight && dY < 0) {
                dY2 = calculateScrollDistance(view2.getTop() - openHeight, dY, -(openHeight - itemHeight), 0);
            } else if (view2.getTop() <= openHeight && dY >= 0) {
                dY2 = calculateScrollDistance(view2.getTop() - openHeight, dY, -(openHeight - itemHeight), 0);
            }
        } else if (openType == OpenType.FULL_SCREEN) {
            if (dY < 0) {
                int itemScrollY = (dY - row) / row;
                calendarDateView.viewChangeWithScrollDistance(itemScrollY);
                dY2 = calculateScrollDistance(view2.getTop() - fullScreenHeight, itemScrollY * 6, -(fullScreenHeight - openHeight), 0);
            } else if (dY >= 0) {
                int itemScrollY = (dY + row) / row;
                calendarDateView.viewChangeWithScrollDistance(itemScrollY);
                dY2 = calculateScrollDistance(view2.getTop() - fullScreenHeight, itemScrollY * 6, -(fullScreenHeight - openHeight), 0);
            }
        }

        if (dY1 != 0) {
//            ViewCompat.offsetTopAndBottom(calendarDateView, dY1);
            calendarDateView.offsetTopAndBottom(dY1);
        }
        if (dY2 != 0) {
//            ViewCompat.offsetTopAndBottom(view2, dY2);
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && openType == FULL_SCREEN) {
                view2.layout(view2.getLeft(), view2.getTop() + dY2, view2.getRight(), view2.getBottom() + dY2);
            } else {
                view2.offsetTopAndBottom(dY2);
            }
        }
    }

    private int calculateScrollDistance(int top, int dY, int minValue, int maxValue) {
//        Log.d(TAG, "calculateScrollDistance " + "open type : " + openType.getName()
//                + " top : " + top + " dY: " + dY + " minValue: " + minValue + " maxValue : " + maxValue);
        if (top + dY < minValue) {
            return minValue - top;
        }

        if (top + dY > maxValue) {
            return maxValue - top;
        }
        return dY;
    }

    int oldY = 0;

    @Override
    public void computeScroll() {
        super.computeScroll();

        if (mScroller.computeScrollOffset()) {
            isSliding = true;
            int cy = mScroller.getCurrY();
            int dy = cy - oldY;
            Log.d(TAG, "computeScroll computeScrollOffset " + " dy : " + dy);
            if (dy > 0) {
                scrollDirection = ScrollDirection.DOWN;
            } else if (dy < 0) {
                scrollDirection = ScrollDirection.UP;
            } else {
                scrollDirection = ScrollDirection.NO;
            }
            move(dy);
            oldY = cy;
            postInvalidate();
        } else {
            Log.d(TAG, "computeScroll computeScrollOffset false");
            scrollDirection = ScrollDirection.NO;
            oldY = 0;
            isSliding = false;
            freshOpenType();

            if (!calendarDateView.isHorizontal()) {
                calendarDateView.refreshLeftAndRightCalendarData();
            }
        }
        Log.d(TAG, "computeScroll " + "open type : " + openType.getName());
    }

    enum OpenType {
        FOLD("fold"), OPEN("open"), FULL_SCREEN("full_screen");
        private String name;

        OpenType(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    private enum ScrollDirection {
        DOWN("down"), UP("up"), NO("no");
        private String name;

        ScrollDirection(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

}
